package server;
import java.sql.SQLException;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/server")
public class SecGuard {
	private static final int RANDOM_LEN = 8;
	
	private static byte[] IV = {(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
								(byte)0x00, (byte)0x00, (byte)0x00};
	
	private long TIME_CONSTRAINT = 60000; //milliseconds
	
	/*
	 * A Request for challenge (in the case of Java card)
	 */
	@GET
	@Path("/n/{uid}")
	@Produces(MediaType.TEXT_PLAIN)
	public String requestChallenge(@PathParam("uid") String uid){
		
		System.out.println("--------------Session started------------");
		
		StringUtility.printHex("Tag uid", StringUtility.toByteArray(uid), uid.length()/2);
		
		try{
			
			byte[] challenge = CryptoUtility.generateRandom(RANDOM_LEN);
			
			StringUtility.printHex("Challenge", challenge, challenge.length);
		
			String challengeTime = StringUtility.getCurrentTime();
		
			//query the current max seq number, increase it by 1 for the next entry
			String maxSeqCmd = "select sequence from Tags where uid = " + "'" + uid + "'" 
								+ "order by sequence desc limit 1";
			
			String maxSeq = DatabaseUtility.search(maxSeqCmd, "sequence");
			if (maxSeq.equals("")) maxSeq = "0";
			
			int nextSeq = Integer.parseInt(maxSeq, 16) + 1;
			
			String hexNextSeq = StringUtility.toHexString(nextSeq);
			
			//insert new entry to the db
			String insertCmd = "insert into Tags values ('" + uid + "', '" + hexNextSeq + "', '"
															+ StringUtility.toHexString(challenge)
															+ "','" + challengeTime 
															+ "', '0000-00-00:00:00:00', 'PROCESSING')";
			DatabaseUtility.insert(insertCmd);
			
			return StringUtility.toHexString(challenge);
		}
		catch(Exception e){
			e.printStackTrace();
			return null;
		}
	}
	
	/*
	 * Get response from the tag which is forwarded by the phone application
	 */
	@GET
	@Path("/tagResponse/{uid}/{MAC}/{sequence}/{challenge}/{imei}/{time}")
	@Produces(MediaType.TEXT_PLAIN)
	public String getResponse(@PathParam("uid") String uid, @PathParam("MAC") String Mac,
							  @PathParam("sequence") String sequence, @PathParam("challenge") String challenge,
							  @PathParam("imei") String imei, @PathParam("time") String time){
		try{
			
			String sharedKey = DatabaseUtility.findKey(uid);
			
			String responseTime = StringUtility.getCurrentTime();
			
			if (!challenge.equals("00")){
				
				String query = "select * from Tags where uid = " + "'" + uid 
														 + "' and sequence = '" + sequence 
														 + "' and challenge = '" + challenge + "'";
				
				String status = DatabaseUtility.search(query, "status");
								
				//if the record is found (means the sequence and challange supplied by the URL match the record in the db)
				if (status.equals("PROCESSING")){
					
					//check the mac to see if it is correct
					byte[] bHash = CryptoUtility.hash("HmacSHA1", sharedKey, challenge, uid, sequence, imei, time);
					
					String hash = StringUtility.toHexString(bHash);
					
					//update the entry
					if (hash.equals(Mac)){
					
						String cmd = "update Tags set status = 'SUCCESS', responseTime = '" + responseTime
									+ "' where uid = " + "'" + uid + "' and sequence = '" + sequence 
									+ "' and challenge = '" + challenge + "'";
						
						DatabaseUtility.insert(cmd);
						System.out.println("Success");
						return "Success";
					}
					else{
						String cmd = "update Tags set status = 'FAILURE', responseTime = '" + responseTime 
										+ "' where uid = " + "'" + uid + "' and sequence = '" + sequence 
										+ "' and challenge = '" + challenge + "'";
						DatabaseUtility.insert(cmd);
						System.out.println("Failure");
						return "Failure";	
					}
				}
			}
			else{
				//check if we could find the sequence in the db
				String query = "select * from Tags where uid = " + "'" + uid + "' and sequence = '" + sequence + "'";
				
				String seq = DatabaseUtility.search(query, sequence);
				
				//if there is no record of this sequence in the db 
				if (seq.equals("")){
					
					//check the mac
					byte[] bHash = CryptoUtility.hash("HmacSHA1", sharedKey, "", uid, sequence, imei, time);
					String hash = StringUtility.toHexString(bHash);
					
					if (hash.equals(Mac)){
						//insert an entry to the db
						String cmd = "insert into Tags values('" + uid + "', '" + sequence 
																 + "', '00', '0000-00-00:00:00:00', '"
																 + responseTime + "', 'SUCCESS')";
						DatabaseUtility.insert(cmd);
						return "Success";
					}
					else{
						String cmd = "insert into Tags values('" + uid + "', '" + sequence 
													+ "', '00', '0000-00-00:00:00:00', '" 
													+ responseTime + "', 'FAILURE')";
						DatabaseUtility.insert(cmd);
						return "Failure";
					}
				}
			}
			
			return "";
		}
		catch(Exception e){
			e.printStackTrace();
			return "";
		}
	}
	
	/*
	 * Process encrypted RndB from the tag 
	 */
	@GET
	@Path("/encryptedRndB/{uid}/{encryptedRndB}/{tagContent}")
	@Produces(MediaType.TEXT_PLAIN)
	public String encryptedRndB(@PathParam("uid") String uid, @PathParam("encryptedRndB") String encRndB,
								@PathParam("tagContent") String tagContent){
		try{

			byte[] sharedKey = StringUtility.toByteArray(DatabaseUtility.findKey(uid));
			
			//decrypt the encryptedRndB to get RndB
			byte[] RndB = CryptoUtility.decrypt(IV, sharedKey, StringUtility.toByteArray(encRndB));
			
			//rotate left
			byte[] rotatedRndB = CryptoUtility.rotateLeft(RndB);
			
			//generate RndA
			byte[] RndA = CryptoUtility.generateRandom(RANDOM_LEN);
			
			String challengeTime = StringUtility.getCurrentTime();
			
			//insert an entry to the db
			String cmd = "insert into DESFireTags values ('" + uid + "', '" + StringUtility.toHexString(RndA) 
															 + "', '" + StringUtility.toHexString(RndB) + "', '" 
															 + challengeTime + "', '0000-00-00:00:00:00', 'PROCESSING')";
			DatabaseUtility.insert(cmd);
			
			/*byte[] RndA = {(byte)0x88, (byte)0x9e, (byte)0xd1, (byte)0xa3, (byte)0x1f, (byte)0xc6, (byte)0x56, (byte)0xd4};
			byte[] apostropheRndB = {(byte)0x3c, (byte)0x8b, (byte)0xeb, (byte)0x53, (byte)0xcc, (byte)0x31, (byte)0xc8, (byte)0x18};
			*/
			byte[] firstBlock = CryptoUtility.decrypt(IV, sharedKey, RndA);
			
			byte[] xor = new byte[RndA.length];
			
			for (int i = 0; i < RndA.length; i++){
				xor[i] = (byte)(firstBlock[i] ^ rotatedRndB[i]);
			}
			//byte[] secondChunk = DES.decrypt(firstBlock, key, rotatedRndB);
			byte[] secondBlock = CryptoUtility.decrypt(IV, sharedKey, xor);
			
			String result = StringUtility.toHexString(firstBlock) + StringUtility.toHexString(secondBlock);
			
			System.out.println("-------Debug--------");
			StringUtility.printHex("Encrypted RndB", encRndB);
			StringUtility.printHex("RndA", RndA, RndA.length);
			StringUtility.printHex("RndB", RndB, RndB.length);
			StringUtility.printHex("decrypted RndA_RndB'", result);
			
			return result;
		}
		catch(Exception e){
			e.printStackTrace();
			return "";
		}
	}
	
	/*
	 * Process encrypted RndA' from the tag 
	 */
	@GET
	@Path("/encryptedRndA/{uid}/{encryptedRndA}")
	@Produces(MediaType.TEXT_PLAIN)
	public String getEncryptedRndA(@PathParam("uid") String uid, @PathParam("encryptedRndA") String enc_rotated_RndA){
		try{
			if(enc_rotated_RndA.equals("Failure")){
				String updateCmd = "update DESFireTags set status = 'FAILURE' where uid = '" + uid 
														+ "' order by challengeTime desc limit 1";
				DatabaseUtility.insert(updateCmd);
				return "";
			}
			//query key
			byte[] key = StringUtility.toByteArray(DatabaseUtility.findKey(uid));

			//find the latest challenge that is processing
			String cmd = "select * from DESFireTags where uid = '" + uid + "' order by challengeTime desc limit 1";
			
			//from the record, extract the RndA, RndB (to compare later on)
			String RndA_inDB = DatabaseUtility.search(cmd, "RndA");
			
			String challengeTime = DatabaseUtility.search(cmd, "challengeTime");
			
			byte[] RndB = StringUtility.toByteArray(DatabaseUtility.search(cmd, "RndB"));
						
			//decrypt the encrypted RndA'
			byte[] rotatedRndA = CryptoUtility.decrypt(IV, key, StringUtility.toByteArray(enc_rotated_RndA));
			
			//rotate RndA' right
			byte[] RndA = CryptoUtility.rotateRight(rotatedRndA);
			
			String responseTime = StringUtility.getCurrentTime();
			
			//if the response satisfies the time constraint
			if (StringUtility.subtractTimes(responseTime, challengeTime) > TIME_CONSTRAINT)
				return "Failure. The tag takes too much time to response";
				
			if (!RndA_inDB.equals(StringUtility.toHexString(RndA))){
				//update db
				String updateCmd = "update DESFireTags set status = 'FAILURE', responseTime = '"  + responseTime 
												   + "' where RndA = '" + RndA_inDB + "' AND uid = '" + uid +"'";
				
				DatabaseUtility.insert(updateCmd);
				return "Failure";
			}
			
			//update db
			String updateCmd = "update DESFireTags set status = 'SUCCESS', responseTime = '" + responseTime 
											  + "' where RndA = '" + RndA_inDB + "' AND uid = '" + uid +"'";
			
			DatabaseUtility.insert(updateCmd);
			
			//calculate session key
			byte[] sessionKey = CryptoUtility.sessionKey(RndA, RndB);
			
			//write information to the tag (for logging purpose)
			//the integrity of the info is guaranteed by using the MAC as specified in the specs.
			//only the message is written to the tag, MAC is to be checked by the tag 
			byte[] response = writeNdef(sessionKey, uid, responseTime);
			
			StringUtility.printHex("encrypted RndA'", enc_rotated_RndA);
			StringUtility.printHex("RndA: ", RndA, RndA.length);
			
			//write data to the tag by using session key
			return StringUtility.toHexString(response);
		}
		catch(Exception e){
			e.printStackTrace();
			return "";
		}
	}
	
	
	public static byte[] writeNdef(byte[] key, String uid, String text) throws SQLException{
		
		//unsigned char tmp[] = { 0x00, 0x21, 0xd2, 0x17, 0x07, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69,
		//	 	 	 	 	 0x6f, 0x6e, 0x2f, 0x47, 0x75, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f,
			// 	 	 	 	 0x4a, 0x43, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x90, 0x00};

		byte[] messageBytes = { (byte)0x00, (byte)0x21, (byte)0xd2, (byte)0x17, (byte)0x07, (byte)0x61, (byte)0x70, (byte)0x70, 
								(byte)0x6c, (byte)0x69, (byte)0x63, (byte)0x61, (byte)0x74, (byte)0x69, (byte)0x6f, (byte)0x6e, 
								(byte)0x2f, (byte)0x47, (byte)0x75, (byte)0x61, (byte)0x72, (byte)0x64, (byte)0x69, (byte)0x6e,
								(byte) 0x67, (byte)0x5f, (byte)0x4a, (byte)0x43, (byte)0x70, (byte)0x61, (byte)0x79, (byte)0x6c, 
								(byte)0x6f, (byte)0x61, (byte)0x64, (byte)0x90, (byte)0x00};

		/*NdefRecord record = NdefRecord.fromText("en", text);
		NdefMessage message = new NdefMessage(record);
		
		//byte[] messageBytes = message.toByteArray();
		byte[] bMessage = message.toByteArray();
		byte[] messageBytes = new byte[bMessage.length + 2];
		
		int l = bMessage.length + 2;
		messageBytes[0] = (byte) (bMessage.length >> 8);
		messageBytes[1] = (byte) (bMessage.length);
		
		System.arraycopy(bMessage, 0, messageBytes, 2, bMessage.length);
		
		*/
		//calculate MAC	
		int l = messageBytes.length;
		byte[] Mac = CryptoUtility.calculateMac(IV, key, messageBytes);
		byte[] len = new byte[3];
		len[0] = (byte)(l >> 16);
		len[1] = (byte)(l >> 8);
		len[2] = (byte)(l);
	
		int Lc_int = 11 + messageBytes.length; //Lc is the length of wrapped data
		byte Lc = (byte)Lc_int;
		
		byte[] cmdApdu = new byte[17 + messageBytes.length];
		cmdApdu[0] = (byte)0x90;
		cmdApdu[1] = (byte)0x3D;
		cmdApdu[2] = (byte)0x00;
		cmdApdu[3] = (byte)0x00;
		cmdApdu[4] = Lc;

		cmdApdu[5] = (byte)0x02; //file id
		cmdApdu[6] = (byte)0x00; //the next three bytes are offset
		cmdApdu[7] = (byte)0x00;
		cmdApdu[8] = (byte)0x00;
		
		cmdApdu[9] = len[2]; //this 3-byte: length of data that is written in the tag (from LSB to MSB)
		cmdApdu[10] = len[1];
		cmdApdu[11] = len[0];
		
		System.arraycopy(messageBytes, 0, cmdApdu, 12, messageBytes.length);
		System.arraycopy(Mac, 0, cmdApdu, 12 + messageBytes.length, 4);
		
		cmdApdu[16 + messageBytes.length] = (byte)0x00;
		
		return cmdApdu;
	}

}
